/*
 * interface for the simulated machine
 *
 * registers are an array, reg[0] to reg[REGISTER_COUNT-1].
 * some registers are reserved for system operations.
 */
#include "env.h"
#ifndef __MACHINE_H
#define __MACHINE_H

#define MAX_STACK_SIZE		0x1000
#define REGISTER_COUNT		16
#define RES					(REGISTER_COUNT-1) /* result register */
#define RET					(REGISTER_COUNT-2) /* return address register */

typedef struct
{
	void* stack[MAX_STACK_SIZE];
	int sp ; /* stack pointer */
	int fp ; /* frame(base) pointer */

	void* reg[REGISTER_COUNT] ;  /* for RES register */
} VirtualHardware ;

extern VirtualHardware machine ;

/*
 * this also pushes the dummy frame, with a NULL return address. make sure that this
 * address is not jumped to.
 */
void initVirtualHardware(void) ;
void cleanVirtualHardware () ;

void push(void* x) ;
void* pop(void) ;

void pushFrame (SchemeObject* args[], Amount argCount, Environment env, void* returnAddress) ;

/*
 * since pushing a frame is usually done by application, which can have 1000 arguments, a convenience
 * function is provided to push the rest of the frame after arguments have been pushed.
 */
void pushPartialFrame(Amount argCount, Environment env, void* returnAddress) ;

void pushDummyFrame(void* returnAddress) ;

/* frame management at stack level */
#define STACK_OLD_FP()		((int)machine.stack[machine.fp-1])
#define STACK_RET_ADDRESS() (machine.stack[machine.fp-2])
#define STACK_ENV()			((Environment)machine.stack[machine.fp-3])
#define STACK_ARGC()		((int)machine.stack[machine.fp-4])
#define STACK_ARGS()		((SchemeObject**)&machine.stack[machine.fp-5])
#define STACK_ARGV(n)		((SchemeObject*)machine.stack[machine.fp-5-n])
#define STACK_FRAME_SIZE()	(STACK_ARGC() + 4)

#define DEBUG_ASSERT_STACK() DEBUG_ASSERT(0 <= machine.sp && machine.sp < MAX_STACK_SIZE && \
									machine.fp < MAX_STACK_SIZE)

/*
 * CALL assumes that arguments have already been pushed
 */
#define CALL(f, argCount, env, returnAddress) \
	do \
	{ \
		DEBUG_TRACE("calling %s, sp = %d, fp = %d", #f, machine.sp, machine.fp) ; \
		pushPartialFrame(argCount, env, returnAddress) ; \
		goto f ; \
	} while (FALSE)

#define RETURN() \
	do \
	{ \
		DEBUG_TRACE("returning to: %x, res = %x, sp = %d, fp = %d", (int)machine.reg[RET],(int)machine.reg[RES], machine.sp, machine.fp) ; \
		goto *machine.reg[RET] ; \
	} while (FALSE)


#define FUNCTION_ENTER(name...) \
	do \
	{ \
		DEBUG_ENTER("function %s, sp = %d, fp = %d", #name, machine.sp, machine.fp) ; \
		push ((void*)machine.fp); \
		machine.fp = machine.sp; \
	} while (FALSE)

/*
 * here, fp is already assigned to the new frame, so we cant use STACK_ARGC, but we know
 * for sure that sp-2 points to argc
 */
#define FUNCTION_EXIT(name...) \
	do \
	{ \
		\
		DEBUG_EXIT("function %s, sp = %d, fp = %d", #name, machine.sp, machine.fp) ; \
		machine.fp = (int)pop() ; \
		machine.reg[RET] = pop() ; \
		machine.sp -= 2 + (int)machine.stack[machine.sp-2] ; \
		RETURN() ; \
	} while (FALSE)



#endif /* __MACHINE_H */

